﻿using System;
using System.Threading.Tasks;
using System.Collections;
using System.Collections.Generic;

using CtpApi.Struct;
using CtpApi.CtpInterface;

using TraderClient.Models;

namespace TraderClient.Logic.QuotationsCenter
{
    /// <summary>
    /// 行情处理类
    /// </summary>
    public class QuoteManager
    {
        /// <summary>
        /// 订阅的合约列表
        /// </summary>
        private static string[] quoteInstruments;

        /// <summary>
        /// K线级别数组
        /// </summary>
        private static int[] quoteLevels;

        /// <summary>
        /// 是否停止读取行情
        /// </summary>
        private static bool isContinue;

        /// <summary>
        /// CTP行情接口
        /// </summary>
        private MdApi mdApi;

        /// <summary>
        /// 单例模式实例对象
        /// </summary>
        private static QuoteManager instance;

        /// <summary>
        /// 行情数据队列
        /// </summary>
        private Queue<LastPrice> queueLastPrice;

        /// <summary>
        /// 存储所有K线列表的字典
        /// </summary>
        private Dictionary<string, KLineSeries> dictionaryQuotations;


        /******************************** Public ********************************/

        /// <summary>
        /// K线新开事件
        /// </summary>
        public event KLineOpenDelegate KLineOpen;
        public delegate void KLineOpenDelegate(KLineSeries series);

        /// <summary>
        /// K线价格变化事件
        /// </summary>
        public event KLinePriceChangeDelegate KLinePriceChange;
        public delegate void KLinePriceChangeDelegate(KLineSeries series);


        /// <summary>
        /// 设置行情要订阅的合约列表和级别
        /// </summary>
        /// <param name="instruments">合约代码</param>
        /// <param name="levels">行情级别（必须包含一分钟级别）</param>
        public static void SetQuote(string[] instruments, int[] levels)
        {
            quoteInstruments = instruments;
            quoteLevels = levels;
        }

        /// <summary>
        /// 停止读取行情
        /// </summary>
        public static void Release()
        {
            isContinue = false;
        }

        /// <summary>
        /// 取得行情对象实例以订阅行情事件
        /// </summary>
        /// <returns>行情对象实例</returns>
        public static QuoteManager GetInstance()
        {
            if (instance == null)
                instance = new QuoteManager();

            return instance;
        }


        /******************************** Private ********************************/

        /// <summary>
        /// 私有构造函数防止实例化（初始化并订阅合约行情)
        /// </summary>
        /// <param name="instruments">合约代码</param>
        /// <param name="levels">行情级别（必须包含一分钟级别）</param>
        private QuoteManager()
        {
            //持续读取行情数据
            isContinue = true;

            //初始化行情数据队列（默认可缓存100个数据）
            queueLastPrice = new Queue<LastPrice>(100);

            //初始化K线列表字典
            dictionaryQuotations = new Dictionary<string, KLineSeries>(100);

            //将订阅的合约代码放入字典
            foreach (string instrument in quoteInstruments)
            {
                foreach (int i in quoteLevels)
                {
                    KLineSeries series = new KLineSeries(MarketType.Futures, instrument, i, KLineUnit.Minutes, 240 / i + 1);
                    dictionaryQuotations.Add(instrument + "_" + i.ToString(), series);
                }
            }

            string borkerID = "4030";
            string userID = "80002863";
            string password = "888888";

            string[] frontMd = { @"tcp://asp-sim2-md1.financial-trading-platform.com:26213" };
            //string[] frontMd = { @"tcp://cifco-md1.financial-trading-platform.com:41213" };

            //初始化CTP行情接口并订阅最新价格到达事件
            mdApi = new MdApi(borkerID, userID, password, frontMd, quoteInstruments);
            mdApi.LastPriceArrive += OnLastPriceArrive;

            //在新线程中取出队列中的行情数据并在该线程中执行所有策略运算
            Task.Factory.StartNew(this.GetPriceFromMdApi);
        }


        /// <summary>
        /// 将最新行情数据放入队列
        /// </summary>
        /// <param name="code">代码</param>
        /// <param name="price">最新价格</param>
        /// <param name="updateTime">更新时间</param>
        private void OnLastPriceArrive(string code, double price, DateTime updateTime)
        {
            //锁定队列防止多个线程同时放入数据
            lock (queueLastPrice)
            {
                LastPrice lastPrice = new LastPrice() { Market = MarketType.Futures, Code = code, Price = price, UpdateTime = updateTime };
                queueLastPrice.Enqueue(lastPrice);
            }
        }


        /// <summary>
        /// 从队列中获取行情数据
        /// </summary>
        private void GetPriceFromMdApi()
        {
            while (isContinue)
            {
                LastPrice lastPrice = (queueLastPrice.Count > 0 ) ? queueLastPrice.Dequeue() : null;

                if (lastPrice != null)
                    PriceChange(lastPrice);
                else
                    System.Threading.Thread.Sleep(200);
            }

            mdApi.Release();
        }


        /// <summary>
        /// 更新各级别K线价格并引发价格变化事件和K线新增事件
        /// </summary>
        /// <param name="instrumentID">合约代码</param>
        /// <param name="price">最新价格</param>
        /// <param name="updateTime">更新时间</param>
        private void PriceChange(LastPrice lastPrice)
        {
            foreach (int level in quoteLevels)
            {
                //从字典中取出对应的K线序列
                KLineSeries series = dictionaryQuotations[lastPrice.Code + "_" + level.ToString()];
                KLine lastK = series.LastKLine;

                //更新K线的最新价格
                if (lastK != null && lastPrice.UpdateTime.Minute < (lastK.OpenTime.Minute + level))
                {
                    lastK.SetLastPrice(lastPrice.Price);
                }
                //生成新的K线
                else
                {
                    //加入新的K线
                    series.Add(lastPrice.UpdateTime, lastPrice.Price);

                    //引发生成K线事件
                    KLineOpen(series);
                }

                //引发K线价格更新事件
                KLinePriceChange(series);
            }
        }

    }
}
